//
//  NativeUniBridge.m
//  flutter_uniapp_plugin
//
//  Created by qiujun on 2022/9/27.
//

#import "NativeUniBridge.h"

#import "DCUniMP.h"
#define k_AppId1 @"__UNI__11E9B73" // uniapp 示例工程，在 HBuilderX 中新建 uniapp 示例项目可获取示例源码
#define k_AppId2 @"__UNI__B151577"
//当前工程 集成UniMPSDK 的版本号,
/// TODO 每次更新版本的时候,需要手动更新这里的版本号
NSString * const DCUniMPVersion = @"3.4.18";


#ifdef DEBUG

#define QJLog(fmt,...) NSLog((@"===QJ_Debug_Log=====[函数名:%s]\n" "[文件:%s 行号:%d]" fmt "\n"), __FUNCTION__,__FILE_NAME__, __LINE__, ##__VA_ARGS__);

#else

#define QJLog(...);


#endif



@interface NativeUniBridge () <DCUniMPSDKEngineDelegate>

@property (nonatomic, weak) DCUniMPInstance *uniMPInstance; /**< 保存当前打开的小程序应用的引用 注意：请使用 weak 修辞，否则应在关闭小程序时置为 nil */

@end

@implementation NativeUniBridge


/// 单例设计模式
///
//1.声明一个空的静态的单例对象
static NativeUniBridge *bridge = nil ;

+(instancetype)shareInstance{
    return [[NativeUniBridge alloc] init];
}


//重写allocWithZone
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    static dispatch_once_t onceToken; //2.声明一个静态的gcd的单次任务
    dispatch_once(&onceToken, ^{ //3.执行gcd单次任务：对对象进行初始化
        if (bridge == nil) {
            bridge = [super allocWithZone: zone];
        }
    });
    return bridge;
    
}

//另外,为防止外界copy,mutable copy对象,需要重写两个方法,`注意:`必须遵守两个协议:<NSCopying,NSMutableCopying>
- (id)copyWithZone:(NSZone *)zone
{
    return bridge;
}
- (id)mutableCopyWithZone:(NSZone *)zone
{
    return bridge;
}



/***
 本函数,打开小程序前需要做各种参数校验
 具体如下:
 /// 1. 本地 是否存在 指定的小程序 miniAPPId,  (如果不存在,会涉及远程下载流程)
 /// 2. 指定的小程序运行基座SDK 版本环境,和本地的SDK 版本是否匹配兼容  (如果不匹配,则无法运行)
 /// 3. 本地的小程序 miniAPPId,版本是否是指定的版本, (如果本地版本比较低,会涉及远程下载新版本)
 
 
 
 打开成功,返回参数案例:
 ///    Map<String, dynamic> result = {
 ///       'code': 0,
 ///       'message': '打开小程序成功',
 ///      'params': {
 ///         'key1': 'value1',
 ///         'key2': 'value2',
 ///        'key3': 'value3',
 ///         ///......
 ///       }
 ///     };
 
 */

+(void)openMiniAPP:(NSDictionary *)dict result:(FlutterResult)result {
    
    __block NSDictionary *response;
    /// 解析各个参数,对参数进行做 判断和校验
    ///  本地 是否存在 指定的小程序 miniAPPId,  (如果不存在,会涉及远程加载流程)
    NSString *miniAPPId = dict[@"miniAPPId"];
    
    if([DCUniMPSDKEngine isExistsUniMP:miniAPPId]){
        //    if(false){
        ///  指定的小程序运行基座SDK 版本环境,和本地的SDK 版本是否匹配兼容  (如果不匹配,则无法运行)
        ///    3.4.18    ===>  3.4.20
        NSString *miniMPSDKVersion = dict[@"miniMPSDKVersion"];
        QJLog(@"========miniMPSDKVersion===========%@",miniMPSDKVersion);
        BOOL isValid = [self checkSDKVersionIsValid:miniMPSDKVersion];
        
        if (!isValid) {
            NSString * error = [NSString stringWithFormat:@"小程序指定运行环境SDK版本和宿主APP SDK 不兼容,\n 小程序运行SDK 是 %@ \n 宿主APP SDK 是 %@ ;\n 小程序SDK版本 必须小于等于 宿主APP SDK",miniMPSDKVersion, DCUniMPVersion];
            
            response = @{
                @"code" :@100,
                @"message" : error,
            };
            
            result(response);
            return;
        }
        
        ///TODO 本地 指定的小程序 miniAPPId,版本是否是指定的版本, (如果不存在,会涉及远程加载流程)
        /// 小程序 下载url , (需要判断是否有下载 url, 下载之后,需要判断 下载的资源包 是否是小程序包)
        BOOL isRight = [self checkLocalMiniAPPVersionIsRight:dict];
        
        if (!isRight) { /// 本地版本 不是指定版本, 走下载流程,下载后指定版本再打开
            [self gotoDownLoadMiniAPP:dict result:result];
            return;
        }
        
        /// 版本正常,直接打开
        
    }else{
        //不存在,会涉及远程加载流程)
        [self gotoDownLoadMiniAPP:dict result:result];
        return;
        
    }
    
    [self realOpenMiniAPP:dict result:result];
    
}


#pragma mark ======== 小程序包 下载 =======

//不存在MiniAPP,会涉及远程加载流程
+(void)gotoDownLoadMiniAPP:(NSDictionary *)miniInfo result:(FlutterResult)result {
    
    NSString *miniAPPId = miniInfo[@"miniAPPId"];
    
    NSString *miniAPPSourceURL = miniInfo[@"miniAPPSourceURL"];
    
    __block NSDictionary *response;
    
    
    [self downloadMiniAPP:miniAPPSourceURL result:^(NSDictionary *downloadResult) {
        
        /// 读取下载结果状态
        /// 成功就 打开运行小程序
        /// 失败就 弹窗提示 (是否重新下载)
        NSNumber *code = downloadResult[@"code"];
        if ([code isEqualToNumber:@200]){ ///下载成功, (需要将小程序部署到 APP 的运行路径下,后续才可以打开)
            NSString *appFilePath = downloadResult[@"appFilePath"];
            
            // 将应用资源部署到运行路径中
            NSError *error;
            if ([DCUniMPSDKEngine installUniMPResourceWithAppid:miniAPPId resourceFilePath:appFilePath password:nil error:&error]) {
                QJLog(@"小程序 %@ 应用资源文件部署成功，版本信息：%@",miniAPPId,[DCUniMPSDKEngine getUniMPVersionInfoWithAppid:miniAPPId]);
                
                [self realOpenMiniAPP:miniInfo result:result];
                return;
                
                
            }else {
                QJLog(@"小程序 %@ 应用资源部署失败： %@",miniAPPId,error);
                
                response = @{
                    @"code" :@101,
                    @"message" : @"本地不存在该小程序,下载成功后,但是部署失败了"
                };
                result(response);
                return;
                
            }
            
            
        }else{
            /// 下载失败
            response = @{
                @"code" :@101,
                @"message" : @"本地不存在该小程序,且远程下载失败了"
            };
            result(response);
            return;
            
        }
        
    }];
    
    
}

/**
 *下载结果回调
 *
 * @param result The result.
 */
typedef void (^DownloadResult)(NSDictionary *result);


/***
 * 小程序包 真正下载过程
 *
 *下载url,(需要判断是否有下载 url, 下载之后,需要判断 下载的资源包 是否是小程序包)
 *
 *
 * result, 下载结果回调到外部 (成功或者失败的 NSDictionary 结构),
 * 例如
 *
 *       NSDictionary *downloadResult = @{
 @"code" :@200,
 @"message" : @"小程序下载成功",
 @"appFilePath" : appFilePath,
 };
 *
 *
 *TODO  小程序包管理可以进一步 做的更完善一下 (比如分目录存放等)
 *将 小程序包下载在本地的 沙盒路径,做持久化存储(方便做小程序的版本管理)  or 还是下载到 Bundle 路径
 *
 **/

+(void) downloadMiniAPP: (NSString *) url result:(DownloadResult) result{
    
    
    QJLog(@"=========pre_downLoad===thread===========%@",[NSThread currentThread]);
    
    NSURL *appFileURL = [NSURL URLWithString:@"http://poi-img.tantu.com/yzc/test/__UNI__B151577.wgt"];
    
    /// 可以考虑在子线程下载 ,下载中 loading 提示, 下载完成,切回主线程进行 UI 打开操作
    
    dispatch_queue_t queue = dispatch_queue_create("miniAPP_downLoad_queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        
        QJLog(@"=========downLoading===thread===========%@",[NSThread currentThread]);
        //1.获取session
        NSURLSession *session = [NSURLSession sharedSession];
        //使用session创建一个下载任务
        NSURLSessionDownloadTask *downLoadTask = [session downloadTaskWithURL:[NSURL URLWithString:url] completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            //当所有数据下载完成后会调用此block
            //location为下载完成后文件保存的硬盘的临时路径（Temp文件夹下面），如果不处理就会马上删除，一般是将下载好的文件剪切到caches路径下面保存起来
            //此下载方法是边下边写，不会占用太多内存，缺点是只有当所有数据都下载下来后才能调用此block，所有没法知道下载进度
            
            QJLog(@"========下载=== error============%@",error);
            
            QJLog(@"=======下载=== response=============%@",response);
            
            if(error == nil){ /// 下载成功
                
                NSString *appFilePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"__UNI__B151577.wgt"];
                // 复制文件
                [[NSFileManager defaultManager] copyItemAtPath:location.path toPath:appFilePath error:NULL];
                
                /// 下载成功后,可以将持久化保存的包 路劲保存好, 然后根据业务需要进行启动小程序
                QJLog(@"======小程序下载成功：======");
                NSDictionary *downloadResult = @{
                    @"code" :@200,
                    @"message" : @"小程序下载成功",
                    @"appFilePath" : appFilePath,
                };
                result(downloadResult);
                
            } else{
                // 下载失败,可以考虑重新下载, 亦或者静默处理
                
                QJLog(@"======小程序下载失败：======");
                NSDictionary *downloadResult = @{
                    @"code" :@-1,
                    @"message" : @"小程序下载失败",
                };
                
                result(downloadResult);
            }
            
        }];
        //开始任务
        [downLoadTask resume];
        
    });
    
    
}




/// 处理完各种SDK版本,环境判断之后,才真正打开小程序

+(void)realOpenMiniAPP:(NSDictionary *)dict result:(FlutterResult)result {
    
    NSString *miniAPPId = dict[@"miniAPPId"];
    
    __block NSDictionary *response;
    // 获取配置信息
    DCUniMPConfiguration *configuration = [[DCUniMPConfiguration alloc] init];
    
    // 配置启动小程序时传递的数据（目标小程序可在 App.onLaunch，App.onShow 中获取到启动时传递的数据）
    configuration.extraData =  dict[@"extraData"];
    
    // 配置小程序启动后直接打开的页面路径 例：@"pages/component/view/view?action=redirect&password=123456"
    //    configuration.path = @"pages/component/view/view?action=redirect&password=123456";
    
    /// 有指定跳转页面,则跳转到指定的小程序页面
    if (dict[@"jumpPage"]){
        configuration.path = dict[@"jumpPage"];
    }
    
    // 开启后台运行
    configuration.enableBackground = YES;
    
    // 设置打开方式
    configuration.openMode = DCUniMPOpenModePresent;
    
    // 启用侧滑手势关闭小程序
    configuration.enableGestureClose = YES;
    
    __weak __typeof([NativeUniBridge shareInstance])weakSelf = [NativeUniBridge shareInstance];
    
    // 打开小程序
    
    QJLog(@"=========start===thread===========%@",[NSThread currentThread])
    
    /// 保证要在主线程 打开小程序, 所以此处必须切换到主线程
    dispatch_async(dispatch_get_main_queue(), ^{
        [DCUniMPSDKEngine openUniMP:miniAPPId configuration:configuration completed:^(DCUniMPInstance * _Nullable uniMPInstance, NSError * _Nullable error) {
            if (uniMPInstance) {
                weakSelf.uniMPInstance = uniMPInstance;
                response = @{
                    @"code" : @0,
                    @"message" : @"打开小程序成功",
                };
                result(response);
            }else {
                QJLog(@"打开小程序出错：%@",error);
                response = @{
                    @"code" :@102,
                    @"message" : @"本地存在小程序,但是打开失败了"
                };
                
                result(response);
            }
            
        }];
    });
    
    
}




#pragma mark ======== 检查本地 小程序包 是否是指定的版本  =======
//    1.如果是指定版本,则直接运行
//    2. 如果不是指定版本, 则需要将本地小程序包 升级 (或者降级)到指定版本


+ (BOOL) checkLocalMiniAPPVersionIsRight:(NSDictionary *) miniInfo {
    
    
    NSString *miniAPPId = miniInfo[@"miniAPPId"];
    NSString *miniAPPVersion = miniInfo[@"miniAPPVersion"];
    
    /// 获取本地小程序包 版本信息
    NSDictionary * localMiniAPP = [DCUniMPSDKEngine getUniMPVersionInfoWithAppid:miniAPPId];
    
    if(!localMiniAPP) return NO;  /// 没有获取到本地小程序包信息, 则代表不存在或者没部署成功,直接走指定版本的下载流程
    
    /// 做版本 比对
    if ([miniAPPVersion isEqualToString:localMiniAPP[@"name"]]){
        return YES;
    }
    
    return  NO;
}


#pragma mark ======== 小程序 运行SDK版本 兼容判断  =======

/// 检查指定的版本 和宿主APP UniMPSDK 版本是否兼容
///  小程序指定运行的SDK版本 必须 小于或者等于 宿主的 UniMPSDK 版本, 才是兼容可运行的
///   比如小程序 指定SDK版本是 :  3.4.15
///   宿主APP UniMPSDK 版本是:  3.4.18 ,
///   那么 小程序和 宿主APP 是版本兼容的
///    否则 ,不兼容会导致 一些未知异常.
///

+ (BOOL) checkSDKVersionIsValid:(NSString *) version {
    
    QJLog(@"=========开始版本比较=============")
    QJLog(@"=========小程序SDK 版本是 =============%@",version);
    QJLog(@"=========宿主APP SDK 版本是============%@",DCUniMPVersion);
    
    NSArray<NSString *> * miniVerArray = [version componentsSeparatedByString:@"."];
    
    NSArray<NSString *> * DCUniVerArray = [DCUniMPVersion componentsSeparatedByString:@"."];
    
    /// 如果小程序指定的版本号小于3位数,则认为是非法版本.不允许执行
    /// 默认小程序运行版本数据 必须是3位  (比如 1.0.0;   1.0 , 2.0 则非法版本)
    if (miniVerArray.count < 3) {
        QJLog(@"=========小程序SDK 版号非法,必须是 3位数版本号 比如 1.0.0 =============");
        return NO;
    }
    
    for (int i = 0; i < 3;  i++) {
        int DCVerNum = [DCUniVerArray[i] intValue];
        int miniVerNum = [miniVerArray[i] intValue];
        
        if (DCVerNum > miniVerNum){ // 宿主APP SDK 版本 大于 小程序指定版本, 合法兼容
            QJLog(@"=========版本兼容=============");
            return  YES;
            
        } else if (DCVerNum == miniVerNum){ // 宿主APP SDK 版本 等于 小程序指定版本,比较下一位版本号
            continue;
        }else{   // 宿主APP SDK 版本 小于 小程序指定版本, 不兼容
            QJLog(@"=========版本不兼容=============");
            return NO;
        }
    }
    /// 当指定小程序 版本号 大于3位以上, 默认兼容  (第四位版本号是极端 微调,不用考虑)
    /// 比如  小程序是 3.4.18.1
    /// 宿主APP SDK是 3.4.18
    QJLog(@"=========版本兼容=============");
    return YES;
}





+ (void) getUniMPSDKVersion: (NSDictionary *)dict {
    
}

+ (void) canOpenMiniAPP: (NSDictionary *)dict {
    
}

+ (void) sentMessageToMiniAPP: (NSDictionary *)dict {
    
}

+ (void) receiveMessageFromMiniAPP: (NSDictionary *)dict {
    
}




@end
